<?php
/**
 * url解析配置基础类
 *
 * @author Mo yi <root@imoi.cn>
 * @link   http://www.imoi.cn
 * @copyright Copyright &copy; 2010-2012 DuoLam Software LLC
 * @license http://www.imoi.cn/license/
 * @version $Id: dpUrlConfig.php 2799 2012-04-04 16:11:02Z Mo.yi $
 * @package base
 * @since 1.0
 */

class dpUrlConfig extends dpComponent
{
    protected $_uri;

    private $_rules = array();

    public static $module;

    public static $control;

    public static $action;

    public static $modules;

	private $_host;

    private $_urlClass = 'dpUrlRule';

    /**
     * 构造方法，初始化系统对URL解析。
     */
	public function __construct()
	{
		if (isset(D::$config['access']['domain_open'])) {
			$request = new dpRequest();
			$this->_host = $request->getHostInfo();
            unset($requeset);
		}

        if (isset(D::$config['access']['url_route']) && 
            D::$config['access']['url_mode'] == 'path' &&
            D::$config['access']['url_route']) 
        {
            $this->rule = $this->setConfig('access','url_rule');
            $this->urlSuffix = $this->setConfig('access','url_suffix');
            if ($this->getUri() != '/') {
                $uri = explode('.', $this->getUri());
                if (isset($uri[1])) {
                    $suffix = substr(D::$config['access']['url_suffix'], 1);
                    if (preg_match('/\.'.$suffix.'/i', $this->getUri())) {
                        $this->parseRouteNativeGet();
                    } else {

                        dpException::error_404('访问的页面不存在');
                    }
                }
            }
            $this->parseUrlRoute();
        } else if (!$this->getSpecialHost() && !empty(D::$config['access']['domain_open'])) {
			preg_match('/[http|https]:\/\/(\w+)./i',$this->_host,$matches);
			$host = $matches[1];
			$this->setRequestUri(ROOT . $host . $this->getUri());
		}
		$this->parseUri();
		$this->uriMapping();
	}

	protected function getSpecialHost()
	{
        if (isset(D::$config['access']['domain_open']) && !D::$config['access']['domain_open'])
            return true;
        else
            return false;
		$special = D::$config['access']['domain_special'];
		preg_match('/[http|https]:\/\/(\w+)./i',$this->_host,$matches);
		return in_array($matches[1], $special) ? true : false;
	}

	 /**
      * 解析URL
      * 针对 compatible 和 path 两种方式解析
      */
    protected function parseUri()
    {
        if (D::$config['access']['url_mode']=='compatible') {
            if (empty(D::$config['access']['route_name'])) {
                exit('请在配置文件中设置route_name参数');
            }

            if (isset($_GET[D::$config['access']['route_name']])) {
                $path = explode('/', $_GET[D::$config['access']['route_name']]);
                $control = isset($path[0]) ? $path[0] : '';
                $action  = isset($path[1]) ? $path[1] : '';
            } else {
                $control = $action = '';
            }
        } else if (D::$config['access']['url_mode'] == 'path') {
            if (isset(D::$config['access']['url_route']) && D::$config['access']['url_route'])
                $uri = str_replace(D::$config['access']['url_suffix'], '', $this->getUri());
            else
                $uri = $this->getUri();

          	$path = explode('/',$uri);
	        if (!empty($path[1])) {
	          	if ($path[1] == 'index.php') {
	          		$control = empty($path[2]) ? null : $path[2];
	          		$action  = empty($path[3]) ? null : $path[3];
	          		unset($path[0],$path[1],$path[2],$path[3]);
	          	} else {
	          		$control = empty($path[1]) ? null : $path[1];
	          		$action  = empty($path[2]) ? null : $path[2];
	          		unset($path[0],$path[1],$path[2]);
	          	}
	        }
          	$this->_uri = $path;
        }
        $control = explode('.', empty($control) ? '' : $control);
        $action  = explode('.', empty($action)  ? '' : $action);
        self::$control = empty($control[0]) ? D::$config['access']['default_control'] : $control[0];
        self::$action  = empty($action[0])  ? D::$config['access']['default_action']  : $action[0];
        $action = explode('?', self::$action);
        self::$action = $action[0];
    }

    /**
     * 根据url解析出来的结果，找到相应的控制器文件和动作。
     */
    protected function uriMapping()
    {
        if (!$this->parseModule()) {
			$control = self::$control . 'Control';
			$action  = 'action' . ucfirst(self::$action);
			$file    = APP_PATH . DS . 'app' . DS . 'control' . DS . self::$control . 'Control.php';
			is_file($file) or dpException::error_404('控制器找不到'.$control);
			require($file);
			$controller = new $control();
			$this->exe($controller, $action);
    	}
    }

    protected function parseModule()
    {
		if (empty(D::$config['access']['open_module'])) return false;

        $module_file = APP_PATH.DS.'runtime'.DS.'modules.php';
        $module_dir  = APP_PATH.DS.'app'.DS.'module'.DS;
        self::$modules = array();

        if (is_file($module_file)) {
            self::$modules = unserialize(include $module_file);
        } else if(is_dir($module_dir)) {
            $res = opendir($module_dir);
            while ($file = readdir($res)) {
                if (!is_dir($module_dir.$file) || $file == '.' || $file == '..') {
                    continue;
                } else {
                    self::$modules[] = $file;
                }
            }
            !is_dir(APP_PATH.DS.'runtime') && mkdir(APP_PATH.DS.'runtime', true, 0755);
            file_put_contents($module_file, '<?php return \''.serialize(self::$modules).'\';?>');
        } else {
            return false;
        }

        if (in_array(self::$control, self::$modules)) {
            self::$module  = self::$control;
            self::$control = self::$action;
            if (D::$config['access']['url_mode']=='compatible') {
                $path = explode('/', $_GET[D::$config['access']['route_name']]);
                self::$action = empty($path[2]) ? D::$config['access']['default_action'] : $path[2];
            } else {
                if (isset($this->_uri[3]) && !empty($this->_uri[3])) {
                    self::$action = $this->_uri[3];
                } else if (isset($this->_uri[4])  && !empty($this->_uri[4])) {
                    self::$action = $this->_uri[4];
                    unset($this->_uri[4]);
                } else {
                    self::$action = D::$config['access']['default_action'];
                }
            }

            $path = APP_PATH.DS.'app'.DS.'module'.DS.self::$module.DS;
            $parent  = $path . self::$module . 'Module.php';
            if (is_file($parent)) {
                include($parent);
            } else {
                dpException::error_404('找不到模块：'.self::$module);
            }
            $control = self::$control.'Control';
            $file    = $path.'control'.DS.self::$control.'Control.php';
            if (is_file($file))
                include($file);
            else
                dpException::error_404('找不到控制器：'.$control);
            $controller = new $control();
            $action = 'action' . ucfirst(self::$action);
            $this->exe($controller, $action);
            return true;
        }
    }

    protected function parseGetParams()
    {
        if (!empty($this->_uri) && is_array($this->_uri)) {
            foreach ($this->_uri as $k => $v) {
                if (empty($v)) continue;
                if ($k % 2 != 0) $_GET[$v] = !empty($this->_uri[($k+1)]) ? $this->_uri[($k+1)] : null;
            }
        }
    }

    protected function exe(&$controller, &$action)
    {
        if (method_exists($controller, 'actions')) {
            $actionList  = $controller->actions();
            $actionName  = strtolower(substr($action, 6));
            $actions     = $actionName . 'Action';
            if (array_key_exists($actionName, $actionList)) {
                $list    = explode('.',$actionList[$actionName]);
                $actions = $list[count($list)-1];
                D::import($actionList[$actionName]);
                !class_exists($actions) && dpException::error_404('该动作的文件不存在'.$actions);
                $this->parseGetParams();
                $actionClass = new $actions();
                call_user_func_array(array($actionClass, 'run'), $actionClass->setActionParams($actionClass, 'run'));
            } else if (method_exists($controller, $action)) {
                $this->parseGetParams();
                $controller->run();
            } else {
                $this->parseGetParams();
                $controller->init();
                $controller->__action();
            }
        } else if (method_exists($controller, $action)) {
            $this->parseGetParams();
            $controller->run();
        } else {
            $this->parseGetParams();
            $controller->init();
            $controller->__action();
        }
    }

    /**
     * 设置路由情况下的get
     */
    protected function parseRouteNativeGet()
    {
        $uri = $this->getUri();
        $query_string = explode('?', $uri);
        $get = isset($query_string[1]) ? $query_string[1] : '';
        if (empty($get)) return false;
        $get = explode('&', $get);
        foreach ($get as $value) {
            $val = explode('=', $value);
            $_GET[$val[0]] = empty($val[1]) ? '' : $val[1];
        }
    }

    /**
     * 获取兼容的request_uri 
     */
    public function getRequestUri()
    {
        if (isset($_SERVER['HTTP_X_REWRITE_URL']))
            $uri = $_SERVER['HTTP_X_REWRITE_URL'];
        else if (isset($_SERVER['REQUEST_URI']))
            $uri = $_SERVER['REQUEST_URI'];
        else if (isset($_SERVER['ORIG_PATH_INFO'])) {
            $uri = $_SERVER['ORIG_PATH_INFO'];
            if (! empty($_SERVER['QUERY_STRING']))
                $uri .= '?' . $_SERVER['QUERY_STRING'];
        }
        else
            $uri = '';
        return $uri;
    }

    /**
     * 设置request_uri
     */
    public function setRequestUri($uri)
    {
        if(isset($_SERVER['HTTP_X_REWRITE_URL']))
            $_SERVER['HTTP_X_REWRITE_URL'] = $uri;
        else if(isset($_SEVER['REQUEST_URI']))
            $_SERVER['REQUEST_URI'] = $uri;
        else
            $_SERVER['HTTP_X_REWRITE_URL'] = $_SERVER['REQUEST_URI'] = $uri;
    }

    /**
     * 静态方法 返回当前控制器名
     * @return string 
     */
    public static function getController()
    {
        return self::$control;
    }

    /**
     * 静态方法，返回当前动作名
     * @return string
     */
    public static function getAction()
    {
        return self::$action;
    }

    /**
     * 静态方法，返回当前模块名
     * @return string
     */
    public static function getModule()
    {
        return self::$module;
    }

    /**
     * 解析url路由
     */
    protected function parseUrlRoute()
    {
        if (is_array($this->rule)) {
            $this->_rules = new $this->_urlClass($this->rule);
        }

        foreach ($this->rule as $pattern => $route) {
            $info = $this->parseUrl($route);
			if(!empty($info)) { 
				$uri = &$info;
				break;
			}
        }
        $params = $this->parseRouteGet();
        $uris   = empty($uri) ? $this->getRequestUri() : ROOT . $uri . '/' . $params;
        $this->setRequestUri($uris);
    }

    /**
     * 处理解析路由后的get参数
     */
    protected function parseRouteGet()
    {
        $params = null;
        foreach ($_GET as $k => $v) {
            $params .= $k . '/' . (empty($v) ? null : $v . '/');
        }
        return $params;
    }

    /**
     * 创建url地址
     * @param string $route URL路由
     * @param array  $params URL参数
     * @param string $ampersand 参数分隔符，在URL模式为path，并且开启了路由时有效
     */
    public static function createUrl($route, $params = array(), $ampersand = array())
    {
        if (empty($route)) return false;
        if (D::$config['access']['url_mode']=='compatible') {
            return self::createUrlIsParse($route, $params, '&', '=');
        } else if (D::$config['access']['url_mode']=='path') {
            if (D::$config['access']['url_route']) {
                return self::createRouteUrl($route, $params, $ampersand);
            } else {
                return self::createUrlIsParse($route, $params, '/', '/', '', true);
            }
        }
    }

    public static function createRouteUrl(&$route, $params, $ampersand)
    {
        $string = isset($ampersand['string']) ? $ampersand['string'] : '\/';
        $var    = isset($ampersand['var'])    ? $ampersand['var']    : '\/';
        $rules  = D::$config['access']['url_rule'];
        $queryString = '';
        if (is_array($params)) {
            foreach ($params as $n => $v) $queryString .= $n . '/' . $v . '/';
        }
        $r_route  = str_replace('/','\/', $route);
        $r_queryString = str_replace('/','\/', substr($queryString, 0, -1));
        $r_queryString = $r_queryString == '' ? '' : '\/'.$r_queryString;
        $uri = $r_route . $r_queryString;
        $anchor = '';
        if (is_array($rules)) {
            foreach ($rules as $r => $u) {
                if (str_replace('\\', '', $uri) == $u) {
                    $rule = $r;
                    break;
                } else {

                    $y = false;
                    if (preg_match('/[<control:\\w\+>\/<action:\\w\+>]/i', $r) && preg_match('/\w+\/\w+/i', $uri)) {
                        $y = true;
                        $rule = str_replace('\\', '', $uri);
                        break;
                    }
                    if (preg_match('/[<control:\\w\+>'.$string.'<action:\\w\+>]/i', $r) && preg_match('/\w+\/\w+/i', $uri)) {
                        $y = true;
                        $rule = str_replace('\\', '', $uri);
                        break;
                    }
                    if (preg_match('/<control:/i', $r) && preg_match('/\w+/i', $uri)) {
                        $y = true;
                        $rule = str_replace('\\', '', $uri);
                        break;
                    }

                    preg_match_all('/(\w+):(.[^>]*)/', $r, $matche);
                    if (!empty($matche[0])) {
                        $names = $matche[1];
                        $value = $matche[2];
                        $n_uri = str_replace('\\', '', $uri);
                        $url = $r;
                        foreach ($names as $k => $name) {
                            $u   = str_replace("<$name>", $value[$k], $u);
                            preg_match('/\/'.$name.'\/('.$value[$k].')/i', $n_uri, $m);
                            if (!empty($m[0])) {
                                $url = preg_replace('/(<'.$name.':.[^>]*>)/i', $m[1], $url);
                            }
                        }

                        $u = str_replace('/', '\\/', $u);
                        preg_match('/'.$u.'/', str_replace('\\', '', $uri), $out);
                        if (!empty($out[0])) {
                            $t_anchor = explode('#', $uri);
                            $anchor = isset($t_anchor[1]) ? '#'.$t_anchor[1] : '';
                            $rule = $url;
                            break;
                        }
                    }
                }
            }
            unset($r_route, $r_queryString);
            if (!isset($rule)) return '';
        }
        if (!preg_match('/:/', $rule)) {
            return D::homeUrl() . $rule . D::$config['access']['url_suffix'] . $anchor;
        } else if ($y) {
            $string = str_replace('\\', '', $string);
            $var    = str_replace('\\', '', $var);
            return D::homeUrl() . str_replace('/', $string, $route) . D::$config['access']['url_suffix'];
        }
    }

    public static function createUrlIsParse($route, $params, $ampersand, $eq, $trueAmpersand = null, $path = false)
    {
        $url = $param = $module = $control = $action = null;

        $route_name = array();

        if (isset($_GET[D::$config['access']['route_name']])) {
            $route_name = explode('/', $_GET[D::$config['access']['route_name']]);
        }

        $m   = isset($route_name[0]) ? $route_name[0] : '';
        $c   = isset($route_name[1]) ? $route_name[1] : D::$config['access']['default_control'];
        $a   = isset($route_name[2]) ? $route_name[2] : D::$config['access']['default_action'];
        $route = explode('/', $route);

        if (!empty(self::$modules) && in_array($route[0], self::$modules)) {
            $module = $route[0];
            if (!empty($route[1])) $control = $route[1];
            if (!empty($route[2])) $action  = $route[2];
        } else {
            $control = $route[0];
            if (!empty($route[1])) $action = $route[1];
        }

        if (!empty($params)) {
            foreach ($params as $var => $value) $param .= $ampersand.$var.$eq.$value;
        }

        if (is_file(APP_PATH.DS.'.htaccess') || is_file(APP_PATH.DS.'httpd.ini'))
            $rewrite = true;
        else
            $rewrite = false;

        $url = $path ? $rewrite == true ? ROOT : ROOT . 'index.php/' : ROOT.'index.php?'.D::$config['access']['route_name'].'=';

        if ($path) {
            $url .= empty($module)  ? '' : $eq . $module;
            $url .= empty($control) ? '' : $eq . $control;
            $url .= empty($action)  ? '' : $eq . $action;
        } else {
            $r  = empty($module)  ? '' : $module;
            $r .= empty($r) ? $control : '/'.$control;
            $r .= empty($action)  ? '' : '/'.$action;
            $url .= $r;
        }

        return str_replace('//','/',rtrim($url,$ampersand).$param);
    }

    /**
     * 解析url
     */
    public function parseUrl($route)
    {
        $rawPathInfo = trim($this->getUri(), '/');
        $pathInfo    = $this->removeUrlSuffix($rawPathInfo,$this->urlSuffix);
        if (($r = $this->_rules->parseUrl($this,$route,$pathInfo,$rawPathInfo)) !== false)
            return $r;
    }

    /**
     * 解析pathinfo参数
     */
    public function parsePathInfo($pathInfo)
    {
        if ($pathInfo==='')
            return;

        $segs=explode('/',$pathInfo.'/');
        $n=count($segs);

        for ($i=0;$i<$n-1;$i+=2) {

            $key=$segs[$i];
            if($key==='') continue;
            $value=$segs[$i+1];

            if (($pos = strpos($key,'[')) !== false && ($m=preg_match_all('/\[(.*?)\]/',$key,$matches)) > 0) {
                $name = substr($key,0,$pos);

                for ($j = $m-1; $j >= 0; --$j) {
                    if ($matches[1][$j] === '')
                        $value = array($value);
                    else
                        $value = array($matches[1][$j]=>$value);
                }

                $_REQUEST[$name] = $_GET[$name] = $value;
            } else {
                $_REQUEST[$key] = $_GET[$key] = $value;
            }
        }
    }

    /**
     * 去除url的后缀
     */
    public function removeUrlSuffix($pathInfo,$urlSuffix)
    {
        if ($urlSuffix!=='' && substr($pathInfo,-strlen($urlSuffix))===$urlSuffix)
            return substr($pathInfo,0,-strlen($urlSuffix));
        else
            return $pathInfo;
    }

    /**
     * 获取基础uri信息
     */
    protected function getUri()
    {
        if (ROOT == '/')
            $temp = $this->getRequestUri();
        else
            $temp = substr($this->getRequestUri(),strlen(ROOT)-1);
        $param = $this->getRequestUri() ? $temp : null;
        return $param;
    }

}


/**
 * url路由解析类
 *
 * @author Mo yi <root@imoi.cn>
 * @link   http://www.imoi.cn
 * @copyright Copyright &copy; 2010-2012 DuoLam Software LLC
 * @license http://www.imoi.cn/license/
 * @version $Id: dpUrlConfig.php 2799 2012-04-04 16:11:02Z Mo.yi $
 * @package base
 * @since 1.0
 */

class dpUrlRule
{
    public $rule;

    public $route;

    public $hasHostInfo;

    public $routePattern;

    public $references=array();

    public $params;

    /**
     * 构造方法
     */
    public function __construct($rule)
    {
        $this->rule = $rule;
        foreach($this->rule as $pattern => $route) {
            $this->parseRegular($route, $pattern);
        }
    }

    /**
     * 解析正则
     */
    public function parseRegular($route, $pattern)
    {
        $this->route[$route] = trim($route, '/');

        $tr2['/'] = $tr['/'] = '\\/';

        if (strpos($route, '<') !== false && preg_match_all('/<(\w+)>/', $route, $matches)) {
            foreach($matches[1] as $name)
                $this->references[$route][$name] = "<$name>";
        }

        $this->hasHostInfo[$route] =! strncasecmp($pattern,'http://',7) || !strncasecmp($pattern,'https://',8);

        if (preg_match_all('/<(\w+):?(.*?)?>/', $pattern, $matches)) {
            $tokens=array_combine($matches[1],$matches[2]);
            foreach ($tokens as $name => $value) {

                if ($value==='') $value = '[^\/]+';
                $tr["<$name>"] = "(?P<$name>$value)";

                if (isset($this->references[$route][$name]))
                    $tr2["<$name>"] = $tr["<$name>"];
                else
                    $this->params[$route][$name] = $value;
            }
        }

        $p = rtrim($pattern,'*');
        $this->append = $p !== $pattern;
        $p = trim($p,'/');
        $this->template[$route] = preg_replace('/<(\w+):?.*?>/','<$1>',$p);
        $this->pattern[$route]  = '/^'.strtr($this->template[$route],$tr).'\/';

        if ($this->append)   
            $this->pattern[$route] .= '/u';
        else
            $this->pattern[$route] .= '$/u';

        if ($this->references !== array())
            $this->routePattern = '/^'.strtr($this->route[$route],$tr2).'$/u';
    }

    /**
     * 解析路由的url
     */
    public function parseUrl($config,$route,$pathInfo,$rawPathInfo)
    {
        $pathInfo = $config->removeUrlSuffix($rawPathInfo,$config->urlSuffix);

        /*  暂时不对域名做解析
        if($this->hasHostInfo)
            $pathInfo=strtolower('hostInfo').rtrim('/'.$pathInfo,'/');
        */
        $pathInfo.='/';
        if (preg_match($this->pattern[$route].'i',$pathInfo,$matches)) {
            $tr=array();
            if (isset($matches['agr'])) {
                $agr = $matches['agr'];
            }
            foreach($matches as $key=>$value) {
                if(isset($this->references[$route][$key]))
                    $tr[$this->references[$route][$key]]=$value;
                else if(isset($this->params[$route][$key]))
                    $_REQUEST[$key]=$_GET[$key]=$value;
            }

            if ($pathInfo!==$matches[0])
                $config->parsePathInfo(ltrim(substr($pathInfo,strlen($matches[0])),'/'));
            if ($this->routePattern!==null) {
                return strtr($this->route[$route],$tr);
            } else {
                return $this->route[$route];
            }
        } else {
            return false;
        }
    }
}


?>